/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.mindlistening;

import com.googlecode.mindlistening.audio.AudioEncoder;
import com.googlecode.mindlistening.audio.AudioPlayer;
import com.googlecode.mindlistening.audio.FFMpegConverter;
import com.googlecode.mindlistening.audio.VorbisEncoder;
import com.googlecode.mindlistening.domain.Conversation;
import com.googlecode.mindlistening.domain.Media;
import com.googlecode.mindlistening.upload.CouchTagsUploader2;
import com.googlecode.mindlistening.upload.MediaUploader;
import com.googlecode.mindlistening.upload.Red5MediaUploader;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import net.sf.json.JSON;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONBuilder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;



/**
 *
 * @author ryan
 */
public class ConversationControllerImpl implements ConversationController {

    private PropertiesStorage properties;
    private DocumentStorage documentStorage;
    private List<AudioRecordingListener> audioListeners;
    private UploadListener uploadListener;
    private TagsUploader tagsUploader;
    private Pattern serverPattern;
    private AudioPlayer audioPlayer;
    private MP3convertTimer timer;
    private boolean isPlaying;
    private boolean lastFilePlayingIsWAV;
    private SimpleAudioRecorder currentRecorder;
    private Map<String,MediaUploader> typeToUploader;

    public ConversationControllerImpl(DocumentStorage storage, PropertiesStorage properties, TagsUploader tagsUploader, AudioPlayer audioPlayer) {
        this.documentStorage = storage;
        this.properties = properties;
        this.audioListeners = new ArrayList<AudioRecordingListener>();
        this.tagsUploader = tagsUploader;
        this.audioPlayer = audioPlayer;
        serverPattern = Pattern.compile("(https?://[^/]+)/(\\w+)");
        typeToUploader = new HashMap<String, MediaUploader>();
        typeToUploader.put("Red5", new Red5MediaUploader());

    }

    public AudioPlayer getAudioPlayer() {
        return audioPlayer;
    }


    private AudioRecordingState findPersistedAudioRecordingState() {
        AudioRecordingState state = new AudioRecordingState();
        state.setRecordingStarted(getDocumentStorage().getStartDate());
        state.setRecordingStopped(getDocumentStorage().getEndDate());
        if (getDocumentStorage().getFileLocation() != null) {
            state.setRecordingTitle(getDocumentStorage().getFileLocation().getName());
        }
        else {
            state.setRecordingTitle("unsaved document");
        }
        return state;
    }



    public void startRecording() throws LineUnavailableException {
        getDocumentStorage().saveCurrentDocument();
        Date start = new Date();
        getDocumentStorage().setStartDate(start);

        File outputFile =  getFileForDocument(getDocumentStorage().getFileLocation(), ".wav");
        currentRecorder = new SimpleAudioRecorder();

        String selectedMixerIndex = properties.loadProperty("selectedMixerIndex");
        if (selectedMixerIndex == null) selectedMixerIndex = "default";

        currentRecorder.startRecording(outputFile, selectedMixerIndex, 0);
        AudioRecordingState state = new AudioRecordingState();
        state.setRecordingStarted(getDocumentStorage().getStartDate());
        state.setRecordingStopped(null);
        fireAudioChange(state);
    }

    public void stopRecording() {
        currentRecorder.stopRecording();
        Date stop = new Date();
        getDocumentStorage().setEndDate(stop);
        getDocumentStorage().saveCurrentDocument();
        AudioRecordingState state = new AudioRecordingState();
        state.setRecordingStarted(getDocumentStorage().getStartDate());
        state.setRecordingStopped(getDocumentStorage().getEndDate());
        fireAudioChange(state);
    }

    @Override
    public String uploadConversation(String server, String hostType, File imagesDir, Map hostingProperties) throws InterruptedException, IOException {
        File outputFile =  getFileForDocument(getDocumentStorage().getFileLocation(), ".wav");

        fireUploadChange("Saving document...", 10);

        getDocumentStorage().saveCurrentDocument();
        String server_url = findServerURL(server);
        String db     = findDB(server);
        Conversation c = getDocumentStorage().convertToConversation();
        c.setSourceMM(documentStorage.getFileLocation());

        List<File> images = findUploadImages(imagesDir, getDocumentStorage().getStartDate(), getDocumentStorage().getEndDate());
        c.setImages(images);
        
        fireUploadChange("Coverting to MP3...", 20);
        File mp3 = convertToMP3(outputFile);
        if (mp3 == null) {
            throw new IOException("Could not create mp3");
        }
        try {
            //fireUploadChange("Uploading Media...", 50);
            //MediaUploader uploader = typeToUploader.get(hostType);

            //File finalMedia = uploader.convertMedia(mp3);

            //Map mediaDetails = uploader.upload(finalMedia, hostingProperties);
            Media media = new Media();
            //media.setDetails(mediaDetails);
            media.setType(hostType);
            media.setStartdate(getDocumentStorage().getStartDate());
            int mediaLength = (int)(getDocumentStorage().getEndDate().getTime() - getDocumentStorage().getStartDate().getTime()) / 1000;
            media.setMediaLength(mediaLength);
            c.setMedia(media);

        } catch (Exception ex) {
            Logger.getLogger(ConversationControllerImpl.class.getName()).log(Level.SEVERE, null, ex);
        }
        tagsUploader = new CouchTagsUploader2();
        System.out.println("Constructing new uploader");
        fireUploadChange("Uploading..", 50);
        tagsUploader.upload(server_url, db, c, mp3);
        

        //String location = uploadData(server, mp3, documentStorage.getFileLocation());
        String location = server_url + "/_" + db + "/topic.html?" + c.getId();
        getDocumentStorage().setUploadURL(location);
        return location;
    }


    protected List<File> findUploadImages(File dir, final Date start, final Date end) {
        final List<File> files = new ArrayList<File>();
        if (dir != null && dir.exists() && dir.isDirectory()) {
            dir.list(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    System.out.println("File: " + name);
                    if (isImage(name)) {
                        System.out.println("Looking at img: " + name);
                        File img = new File(dir, name);
                        Date imageDate = new Date(img.lastModified());
                        System.out.println("Image date: " + imageDate.toString() + " start: " + start + " end: " + end);
                        if (start.before(imageDate) && end.after(imageDate)) {
                            System.out.println("Added");
                            files.add(img); // shortcut. We really should do this elsewhere.
                            return true;
                        }
                    }
                    return false;
                }
            });
        }
        return files;
    }


    private boolean isImage(String name) {
        if (name.toLowerCase().endsWith(".jpg")) return true;
        if (name.toLowerCase().endsWith(".jpeg")) return true;
        if (name.toLowerCase().endsWith(".gif")) return true;
        return false;

    }


    protected String findServerURL(String string) {
        Matcher m = serverPattern.matcher(string);
        if (m.find()) {
            return m.group(1);
        }
        return "";
    }

    protected String findDB(String string) {
        Matcher m = serverPattern.matcher(string);
        if (m.find()) {
            return m.group(2);
        }
        return "";
    }

    private synchronized File convertToMP3(File outputFile) throws InterruptedException, IOException {
        File mp3 = getFileForDocument(outputFile, ".mp3");
        if (mp3.exists()) return mp3;

        String ffmpegcmd = properties.loadProperty("ffmpegcmd");
        if (ffmpegcmd == null || "".equals(ffmpegcmd)) {
            return null;
        }
        long bitrate;
        try {
            bitrate = Long.parseLong(properties.loadProperty("bitrate")) * 1000;
        } catch (Exception e) {
            bitrate = 24000L;
        }
        long frequency;
        try {
            frequency = Long.parseLong(properties.loadProperty("frequency"));
        } catch (Exception e) {
            frequency = 22050L;
        }



        File mp3Temp = getFileForDocument(outputFile, ".mp3.tmp");
        FFMpegConverter converter = new FFMpegConverter(ffmpegcmd, FFMpegConverter.ENCODER_MP3);
        //System.out.println("Converting to mp3");
        converter.convert(outputFile, bitrate, frequency, mp3Temp, true);
        //System.out.println("Renaming");
        mp3Temp.renameTo(mp3);
        return mp3;
    }

//    protected List<File> findPicturesInDirBetweenDates(File dir, Date start, Date end) {
//        assert(dir.exists() && dir.isDirectory());
//        ArrayList result = new ArrayList<File>();
//        File[] children = dir.listFiles();
//        for (File child : children) {
//           if (child.getName().endsWith(".jpg") || child.getName().endsWith(".jpeg") || child.getName().endsWith(".JPG")) {
//               if (isPictureBetweenDates(child, start, end)) {
//                   result.add(child);
//               }
//            }
//        }
//        return result;
//    }


//    protected boolean isPictureBetweenDates(File picture, Date start, Date end) {
//        Date picDate = null;
//        try {
//            Metadata metadata = JpegMetadataReader.readMetadata(picture);
//            Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);
//            picDate = exifDirectory.getDate(ExifDirectory.TAG_DATETIME_ORIGINAL) ;
//            if (picDate == null) picDate = exifDirectory.getDate(ExifDirectory.TAG_DATETIME);
//        } catch (JpegProcessingException e) {
//        } catch (MetadataException e) {
//        }
//        if (picDate == null) picDate = new Date(picture.lastModified());
//        if (picDate.after(start) && picDate.before(end)) return true;
//
//        return false;
//    }


    private File getFileForDocument(File file, String newExtenstion) {
        String name = file.getName() + newExtenstion;
        File parent = file.getParentFile();
        return new File(parent, name);
    }


    // Right now, only a one to one broadcast! will change if needed
    public void addAudioRecordingListener(AudioRecordingListener listener) {
        this.audioListeners.add(listener);
        AudioRecordingState state = findPersistedAudioRecordingState();
        fireAudioChange(state);
        if (timer == null) {
            timer = new MP3convertTimer();
            timer.start();
        }
    }

    public void removerAudioRecordingListener(AudioRecordingListener listener) {
        this.audioListeners.remove(listener);
        AudioRecordingState state = findPersistedAudioRecordingState();
        fireAudioChange(state);
    }

    @Override
    public void addUploadListener(UploadListener listener) {
        this.uploadListener = listener;
        String uploadURL = getDocumentStorage().getUploadURL();
        if (uploadURL != null ) {
            fireUploadComplete(uploadURL);
        }
    }

    @Override
    public void removeUploadListener(UploadListener listener) {
        this.uploadListener = null;
    }

    private void fireAudioChange(AudioRecordingState state) {
        for (AudioRecordingListener audioRecordingListener : audioListeners) {
            audioRecordingListener.recordingChanged(state);
        }
    }


    private void fireUploadComplete(String url) {
        UploadState state = new UploadState();
        state.setMinorTaskName("Upload Complete...");
        state.setMinorTaskIndeterminate(false);
        state.setMinorTaskPercentComplete(100);
        state.setOverallPercentComplete(100);
        state.setUploadURL(url);
        fireUploadChange(state);
    }

    protected void fireUploadChange(String minorTaskName,int overallPercentComplete) {
        UploadState state = new UploadState();
        state.setMinorTaskName(minorTaskName);
        state.setMinorTaskIndeterminate(true);
        state.setOverallPercentComplete(overallPercentComplete);
        fireUploadChange(state);
    }



    private void fireUploadChange(String minorTaskName,int minorTaskPercentComplete,int overallPercentComplete) {
        UploadState state = new UploadState();
        state.setMinorTaskName(minorTaskName);
        state.setMinorTaskIndeterminate(false);
        state.setMinorTaskPercentComplete(minorTaskPercentComplete);
        state.setOverallPercentComplete(overallPercentComplete);
        fireUploadChange(state);
    }


    private void fireUploadChange(UploadState state) {
        if (uploadListener != null ) {
            uploadListener.uploadChange(state);
        }
    }




    @Override
    public void startPlayback() {

        File file_to_play = getFileForDocument(getDocumentStorage().getFileLocation(), ".wav.mp3");
        lastFilePlayingIsWAV = false;
        if (file_to_play == null || !file_to_play.exists()) {
            file_to_play = getFileForDocument(getDocumentStorage().getFileLocation(), ".wav");
            lastFilePlayingIsWAV = true;
        }

        System.out.append("Trying to play: " + file_to_play.getAbsolutePath());
        isPlaying = true;
;       audioPlayer.play(file_to_play);
    }

    @Override
    public void pausePlayback() {
        audioPlayer.pause();
    }

    @Override
    public void resumePlayback() {
        audioPlayer.resume();
    }

    @Override
    public void stopPlayback() {
        isPlaying = false;
        audioPlayer.stop();
    }

    @Override
    public void seekPlayback(long seconds) {
        audioPlayer.seek(seconds);
    }

    /**
     * @return the documentStorage
     */
    public DocumentStorage getDocumentStorage() {
        return documentStorage;
    }


    public void changeDocument(DocumentStorage docStorage) {
        AudioRecordingState old_recording_state = findPersistedAudioRecordingState();
        if ((old_recording_state.getRecordingStarted() != null && old_recording_state.getRecordingStopped() == null) || isPlaying) {
            //System.out.println("Not changing document");
            return;
        }
        this.documentStorage = docStorage;
        AudioRecordingState new_recording_state = findPersistedAudioRecordingState();
        //System.out.println("Document change: " + new_recording_state.getRecordingTitle());
        this.documentStorage = docStorage;
        fireAudioChange(new_recording_state);

    }




    protected class MP3convertTimer extends Thread {

        @Override
        public void run() {
            while(true) {
                AudioRecordingState state = findPersistedAudioRecordingState();
                if (state.getRecordingStopped() != null) {
                    File wav =  getFileForDocument(getDocumentStorage().getFileLocation(), ".wav");
                    File mp3 = null;
                    try {
                        mp3 = convertToMP3(wav);
                    } catch (Exception ex) {
                        Logger.getLogger(ConversationControllerImpl.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    if (mp3 != null) {
                        if (!isPlaying  || !lastFilePlayingIsWAV) {
                            // its ok to delete the wav file
                            wav.delete();
                        }
                    }



                }
                sleep();
            }
        }


        private void sleep() {
            try {
                sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(ConversationControllerImpl.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
